const { SyncHook } = require("tapable"); //引入SyncHook
const Compilation = require("./compilation");
const path = require("path");
const fs = require("fs");

//Compiler其实是一个类，它是整个编译过程的大管家，而且是单例模式
class Compiler {
  constructor(webpackOptions) {
    this.options = webpackOptions; //存储配置信息

    //它内部提供了很多钩子
    this.hooks = {
      run: new SyncHook(), //会在编译刚开始的时候触发此run钩子
      done: new SyncHook(), //会在编译结束的时候触发此done钩子
    };
  }

  //虽然webpack只有一个Compiler，但是每次编译都会产出一个新的Compilation，
  //这里主要是为了考虑到watch模式，它会在启动时先编译一次，然后监听文件变化，如果发生变化会重新开始编译
  //每次编译都会产出一个新的Compilation，代表每次的编译结果
  compile(onCompiled) {
    let compilation = new Compilation(this.options);
    compilation.build(onCompiled); //执行compilation的build方法进行编译，编译成功之后执行回调
  }

  run(callback) {
    this.hooks.run.call(); //在编译前触发run钩子执行，表示开始启动编译了
    const onCompiled = (err, stats, fileDependencies) => {
      this.hooks.done.call(); //当编译成功后会触发done这个钩子执行
      //第十步：确定好输出内容之后，根据配置的输出路径和文件名，将文件内容写入到文件系统（这里就是硬盘）
      for (let filename in stats.assets) {
        let filePath = path.join(this.options.output.path, filename);
        console.log("filepath", filePath);
        // 先判断文件是否存在，不存在则创建
        if (!fs.existsSync(this.options.output.path)) {
          fs.mkdirSync(this.options.output.path, { recursive: true });
        }
        fs.writeFileSync(filePath, stats.assets[filename], "utf8");
      }

      // 成功之后执行回调
      callback(err, {
        toJson: () => stats,
      });

      //第十一步：监听文件变化，如果文件发生变化，重新编译
      fileDependencies.forEach((fileDependencies) => {
        fs.watch(fileDependencies, () => this.compile(onCompiled));
      });

      // 全部流程做完
      this.hooks.done.call(); //当编译成功后会触发done这个钩子执行
    };

    this.compile(onCompiled); //开始编译，成功之后调用onCompiled
  }
}
//第一步：搭建结构，读取配置参数，这里接受的是webpack.config.js中的参数
function webpack(webpackOptions) {
  const compiler = new Compiler(webpackOptions);
  const { plugins = [] } = webpackOptions;

  plugins.forEach((plugin) => {
    plugin.apply(compiler);
  });

  return compiler;
}

module.exports = webpack;
